'use strict';

class middleware {
  constructor (options = {}) {
    this.chain = [];
    this.stack = [];
    this.cache = false;

    this.init();

    if (options && typeof options === 'object') {
      if (options.cache !== undefined) {
        this.cache = options.cache;
      }
    }

  }

  init () {
    this.chain = [];
    let first = async (ctx) => {
      if (ctx && typeof ctx === 'object' && ctx.exec && typeof ctx.exec === 'function') {
        return await ctx.exec(ctx);
      }
    };

    this.chain.push(first);
  }

  add (midcall, filter = null) {
    if (typeof midcall !== 'function' || midcall.constructor.name !== 'AsyncFunction') {
      throw new Error(`middleware must be a async function`);
    }

    let last = this.chain.length - 1;
    let nextcall = this.chain[last];

    let realmid = function () {
      if (filter !== null && typeof filter === 'function') {
        return async (ctx) => {
          if ( false === await filter(ctx) ) {
            return await nextcall(ctx);
          }
          return await midcall(ctx, nextcall.bind(null, ctx));
        };
      }

      return async (ctx) => {
        return await midcall(ctx, nextcall.bind(null, ctx));
      };

    };

    this.chain.push(realmid());
  }

  loadstack () {
    this.init();
    for (let i = this.stack.length - 1; i >= 0; i--) {
      this.add(
        this.stack[i].midcall,
        this.stack[i].filter
      );
    }
  }

  use (midcall, filter = null) {
    this.stack.push({
      midcall: midcall,
      filter:  filter
    });
    if (this.cache === false) {
      this.loadstack();
    }
  }

  async run (ctx) {
    let last = this.chain.length - 1;
    return await this.chain[last](ctx);
  }

}

module.exports = middleware;
